/*
 * Copyright 2009 Rohan Ranade
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.code.fcr.ui;

import com.google.code.fcr.Card;
import com.google.code.fcr.CardRenderConfig;
import com.google.code.fcr.CardRenderer;
import com.google.code.fcr.CardTableModel;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.table.TableModel;

/**
 *
 * @author Rohan Ranade
 */
public class MainApplicationVisual extends javax.swing.JFrame 
        implements ListSelectionListener, TableModelListener, DocumentListener {

    /** Creates new form MainApplicationVisual */
    public MainApplicationVisual() {
        initComponents();
        this.setIconImage(new ImageIcon(this.getClass().getResource("cards.png")).getImage());
        cardsTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        cardsTable.getSelectionModel().addListSelectionListener(this);
        cardsTable.getModel().addTableModelListener(this);
        outputDirText.getDocument().addDocumentListener(this);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        cardsTable = new javax.swing.JTable();
        addButton = new javax.swing.JButton();
        moveUpButton = new javax.swing.JButton();
        moveDownButton = new javax.swing.JButton();
        removeButton = new javax.swing.JButton();
        outputDirText = new javax.swing.JTextField();
        browseButton = new javax.swing.JButton();
        generateButton = new javax.swing.JButton();
        closeButton = new javax.swing.JButton();
        editButton = new javax.swing.JButton();
        importButton = new javax.swing.JButton();
        exportButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Flash Card Renderer");
        setResizable(false);

        jLabel1.setText("Output Directory:");

        cardsTable.setModel(new CardTableModel());
        jScrollPane1.setViewportView(cardsTable);

        addButton.setText("Add...");
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonActionPerformed(evt);
            }
        });

        moveUpButton.setText("Move Up");
        moveUpButton.setEnabled(false);
        moveUpButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveUpButtonActionPerformed(evt);
            }
        });

        moveDownButton.setText("Move Down");
        moveDownButton.setEnabled(false);
        moveDownButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                moveDownButtonActionPerformed(evt);
            }
        });

        removeButton.setText("Remove");
        removeButton.setEnabled(false);
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonActionPerformed(evt);
            }
        });

        outputDirText.setEditable(false);

        browseButton.setText("Browse...");
        browseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                browseButtonActionPerformed(evt);
            }
        });

        generateButton.setText("Render Cards");
        generateButton.setEnabled(false);
        generateButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                generateButtonActionPerformed(evt);
            }
        });

        closeButton.setText("Close");
        closeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButtonActionPerformed(evt);
            }
        });

        editButton.setText("Edit");
        editButton.setEnabled(false);
        editButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                editButtonActionPerformed(evt);
            }
        });

        importButton.setText("Import...");
        importButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                importButtonActionPerformed(evt);
            }
        });

        exportButton.setText("Export...");
        exportButton.setEnabled(false);
        exportButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exportButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(outputDirText, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(browseButton))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 506, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(removeButton)
                            .addComponent(moveDownButton)
                            .addComponent(moveUpButton)
                            .addComponent(editButton)
                            .addComponent(addButton)
                            .addComponent(exportButton)
                            .addComponent(importButton)))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(generateButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(closeButton)))
                .addContainerGap())
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {addButton, browseButton, editButton, exportButton, importButton, moveDownButton, moveUpButton, removeButton});

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {closeButton, generateButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel1)
                    .addComponent(outputDirText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(browseButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 291, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(importButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(exportButton)
                        .addGap(18, 18, 18)
                        .addComponent(addButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(editButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(moveUpButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(moveDownButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(removeButton)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(generateButton)
                    .addComponent(closeButton))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
        // TODO add your handling code here:
        JFileChooser chooser = new JFileChooser();
        chooser.setFileHidingEnabled(true);
        chooser.setMultiSelectionEnabled(false);
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        chooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory();
            }

            @Override
            public String getDescription() {
                return "Directories";
            }
        });
        chooser.setDialogTitle("Select output directory");
        int retVal = chooser.showDialog(this, "Select");
        if (retVal == JFileChooser.APPROVE_OPTION) {
            File outputDir = chooser.getSelectedFile();
            outputDirText.setText(outputDir.getAbsolutePath());
        }
    }//GEN-LAST:event_browseButtonActionPerformed

    private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
        CardEditor editor = new CardEditor(this, true);
        editor.setVisible(true);
        if (editor.getReturnStatus() == CardEditor.RET_OK) {
            addCard(editor.getCard());
        }
    }//GEN-LAST:event_addButtonActionPerformed

    private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
        int idx = cardsTable.getSelectedRow();
        CardTableModel dtm = (CardTableModel) cardsTable.getModel();
        dtm.removeCard(idx);
    }//GEN-LAST:event_removeButtonActionPerformed

    private void editButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editButtonActionPerformed
        int idx = cardsTable.getSelectedRow();
        CardTableModel dtm = (CardTableModel) cardsTable.getModel();
        Card card = dtm.getCardAt(idx);
        CardEditor editor = new CardEditor(this, true);
        editor.setCard(card);
        editor.setVisible(true);
        if (editor.getReturnStatus() == CardEditor.RET_OK) {
            dtm.setCardAt(editor.getCard(), idx);
        }
    }//GEN-LAST:event_editButtonActionPerformed

    private void moveUpButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveUpButtonActionPerformed
        int idx = cardsTable.getSelectedRow();
        CardTableModel dtm = (CardTableModel) cardsTable.getModel();
        dtm.moveUp(idx);
        cardsTable.getSelectionModel().setSelectionInterval(idx - 1, idx - 1);
    }//GEN-LAST:event_moveUpButtonActionPerformed

    private void moveDownButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_moveDownButtonActionPerformed
        int idx = cardsTable.getSelectedRow();
        CardTableModel dtm = (CardTableModel) cardsTable.getModel();
        dtm.moveDown(idx);
        cardsTable.getSelectionModel().setSelectionInterval(idx + 1, idx + 1);
    }//GEN-LAST:event_moveDownButtonActionPerformed

    private void generateButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_generateButtonActionPerformed
        final List<Card> cards = ((CardTableModel) cardsTable.getModel()).getCards();
        final String outputPath = outputDirText.getText();
        RenderingWorker worker = new RenderingWorker(new File(outputPath), cards);
        GenericProgressDialog gpd = new GenericProgressDialog(worker, this, true);
        gpd.setVisible(true);
    }//GEN-LAST:event_generateButtonActionPerformed

    private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed
        System.exit(0);
    }//GEN-LAST:event_closeButtonActionPerformed

    private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed
        JFileChooser chooser = new JFileChooser();
        chooser.setFileHidingEnabled(true);
        chooser.setMultiSelectionEnabled(false);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() || f.getName().endsWith(".csv");
            }

            @Override
            public String getDescription() {
                return "CSV Files";
            }
        });
        int retVal = chooser.showOpenDialog(this);
        if (retVal == JFileChooser.APPROVE_OPTION) {
            File inputFile = chooser.getSelectedFile();
            CardTableModel tm = (CardTableModel) cardsTable.getModel();
            ImportWorker iw = new ImportWorker(tm, inputFile);
            GenericProgressDialog gpd = new GenericProgressDialog(iw, this, true, true);
            gpd.setVisible(true);
        }
    }//GEN-LAST:event_importButtonActionPerformed

    private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
        JFileChooser chooser = new JFileChooser();
        chooser.setFileHidingEnabled(true);
        chooser.setMultiSelectionEnabled(false);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() || f.getName().endsWith(".csv");
            }

            @Override
            public String getDescription() {
                return "CSV Files";
            }
        });
        int retVal = chooser.showSaveDialog(this);
        if (retVal == JFileChooser.APPROVE_OPTION) {
            File outputFile = chooser.getSelectedFile();
            final List<Card> dataList = ((CardTableModel) cardsTable.getModel()).getCards();
            ExportWorker worker = new ExportWorker(outputFile, dataList);
            GenericProgressDialog gpd = new GenericProgressDialog(worker, this, true);
            gpd.setVisible(true);
        }
    }//GEN-LAST:event_exportButtonActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addButton;
    private javax.swing.JButton browseButton;
    private javax.swing.JTable cardsTable;
    private javax.swing.JButton closeButton;
    private javax.swing.JButton editButton;
    private javax.swing.JButton exportButton;
    private javax.swing.JButton generateButton;
    private javax.swing.JButton importButton;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton moveDownButton;
    private javax.swing.JButton moveUpButton;
    private javax.swing.JTextField outputDirText;
    private javax.swing.JButton removeButton;
    // End of variables declaration//GEN-END:variables

    private void addCard(Card card) {
        TableModel tm = cardsTable.getModel();
        CardTableModel dtm = (CardTableModel) tm;
        dtm.appendCard(card);
    }

    public void valueChanged(ListSelectionEvent e) {
        int selCount = cardsTable.getSelectedRowCount();
        int selIdx = cardsTable.getSelectedRow();
        if (selCount > 0) {
            editButton.setEnabled(true);
            removeButton.setEnabled(true);
            if (selIdx > 0) {
                moveUpButton.setEnabled(true);
            } else {
                moveUpButton.setEnabled(false);
            }
            if (selIdx < cardsTable.getRowCount() - 1) {
                moveDownButton.setEnabled(true);
            } else {
                moveDownButton.setEnabled(false);
            }
        } else {
            editButton.setEnabled(false);
            removeButton.setEnabled(false);
            moveUpButton.setEnabled(false);
            moveDownButton.setEnabled(false);
        }
    }

    public void tableChanged(TableModelEvent e) {
        processTableModelUpdate();
    }

    public void insertUpdate(DocumentEvent e) {
        processTableModelUpdate();
    }

    public void removeUpdate(DocumentEvent e) {
        processTableModelUpdate();
    }

    public void changedUpdate(DocumentEvent e) {
        processTableModelUpdate();
    }

    private void processTableModelUpdate() {
        if ((outputDirText.getText().trim().length() > 0) && (cardsTable.getRowCount() > 0)) {
            generateButton.setEnabled(true);
        } else {
            generateButton.setEnabled(false);
        }

        if (cardsTable.getRowCount() > 0) {
            exportButton.setEnabled(true);
        } else {
            exportButton.setEnabled(false);
        }
    }

    private static final class ExportWorker extends SwingWorker<Void, Void> {

        private final File outputFile;
        private final List<Card> data;

        public ExportWorker(File outputFile, List<Card> data) {
            this.outputFile = outputFile;
            this.data = data;
        }

        @Override
        protected Void doInBackground() throws Exception {
            PrintWriter pw = new PrintWriter(outputFile);
            int idx = 0;
            int rowCount = data.size();
            for (Card r : data) {
                pw.printf("%s,%s,%s\n", r.getName(), r.getHint(), r.getDescription());
                setProgress(idx * 100 / rowCount);
            }
            pw.flush();
            pw.close();
            return null;
        }
    }

    private static final class ImportWorker extends SwingWorker<Void, Card> {

        private final CardTableModel dtm;
        private final File inputFile;

        public ImportWorker(CardTableModel dtm, File inputFile) {
            this.dtm = dtm;
            this.inputFile = inputFile;
        }

        @Override
        protected Void doInBackground() throws Exception {
            BufferedReader br = new BufferedReader(new FileReader(inputFile));
            String line = null;
            while ((line = br.readLine()) != null) {
                String[] splits = line.split(",");
                Card r = new Card(splits[0].trim(), splits[1].trim(), splits[2].trim());
                publish(r);
            }
            br.close();
            return null;
        }

        @Override
        protected void process(List<Card> chunks) {
            for (Card row : chunks) {
                dtm.appendCard(row);
            }
        }
    }

    private static final class RenderingWorker extends SwingWorker<Void, Void> {

        private final File dir;
        private final List<Card> datum;

        public RenderingWorker(File dir, List<Card> datum) {
            this.dir = dir;
            this.datum = datum;
        }

        @Override
        protected Void doInBackground() throws Exception {
            CardRenderer renderer = new CardRenderer();
            CardRenderConfig config = new CardRenderConfig();
            renderer.setRenderConfig(config);
            int count = datum.size();
            int current = 0;
            for (Card row : datum) {
                File f = new File(dir, nextFileName(current));
                BufferedImage img = renderer.renderCard(row);
                ImageIO.write(img, CardRenderer.PNG_FORMAT, f);
                current++;
                setProgress(current * 100 / count);
            }
            return null;
        }

        private String nextFileName(int counter) {
            return "card-" + String.valueOf(counter) + ".png";
        }
    }
}
